package server;

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.math.BigInteger;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.List;

import model.ActivationDetails;
import model.AdminControlMessage;
import model.Candidate;
import model.LoginDetails;
import model.RegistrationDetails;
import model.User;
import model.UserSession;
import model.Vote;
import model.VoteDetails;
import model.VoteResult;
import dao.CandidateDAO;
import dao.UserDAO;
import dao.VoteDAO;
import encryption.Paillier;

public class ServerThread extends Thread {

	private ObjectInputStream objectIn;
	private ObjectOutputStream objectOut;
	private Socket connectionSocket;
	private UserDAO userDAO;
	private CandidateDAO candidateDAO;
	private VoteDAO voteDAO;
	private Server server;
	private int userID;

	public ServerThread(Server server, Socket socket) throws Exception {
		this.server = server;
		this.userID = 0;
		try {
			connectionSocket = socket;
			System.out.println("Client connected.");
			objectIn = new ObjectInputStream(connectionSocket.getInputStream());
			objectOut = new ObjectOutputStream(connectionSocket
					.getOutputStream());
			userDAO = new UserDAO();
			candidateDAO = new CandidateDAO();
			voteDAO = new VoteDAO();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	@Override
	public void run() {
		try {
			while (true) {
				Object receivedObject = objectIn.readObject();
				if (receivedObject.getClass() == String.class) {

					if (((String) receivedObject).equals("get live feed")) {
						List<Vote> votes = userDAO.latestVotes();
						objectOut.writeObject(votes);
						objectOut.flush();

					} else

					if (((String) receivedObject).equals("get public key")) {
						objectOut.writeObject(Paillier.getPublicKey());
						objectOut.flush();
					} else

					if (((String) receivedObject).equals("get results")) {
						List<VoteResult> voteResults = Paillier
								.getVotersPerCandidate(voteDAO.getAllVotes());

						Collections.sort(voteResults);
						objectOut.writeObject(voteResults);
						objectOut.flush();
					} else if (((String) receivedObject)
							.equals("get user session")) {
						int sessionID;
						sessionID = this.getRandomSessionID();
						Calendar expiryDate = new GregorianCalendar();
						expiryDate.add(Calendar.MINUTE, 30);
						UserSession userSession = new UserSession();
						userSession.setSessionID(sessionID);
						userSession.setExpiryDate(expiryDate.getTime());

						User user = userDAO.getUserById(userID);
						user.setSession(userSession);
						userDAO.commitUser(user);

						objectOut.writeObject(userSession);
						objectOut.flush();

					} else if (((String) receivedObject)
							.equals("get voting status")) {
						if (this.server.getVotingStartDate() == null) {
							objectOut
									.writeObject("Sorry, voting hasn't started yet.");
						} else if (this.server.getVotingEndDate() != null) {
							objectOut.writeObject("Sorry, voting has closed.");
						} else {

							User user = userDAO.getUserById(this.userID);
							if (user.getVote() != null) {
								objectOut.writeObject("You have voted for: "
										+ user.getVote());
							} else {
								objectOut.writeObject("user hasn't voted");
							}
						}
						objectOut.flush();
					} else if (((String) receivedObject)
							.equals("get candidates list")) {
						if (this.server.getVotingStartDate() == null) {
							objectOut
									.writeObject("Sorry, voting hasn't started yet.");
						} else if (this.server.getVotingEndDate() != null) {
							objectOut.writeObject("Sorry, voting has closed.");
						} else {
							List<Candidate> candidatesList = candidateDAO
									.getAllCandidates();
							objectOut.writeObject(candidatesList);
						}
						objectOut.flush();
					}

					else if (((String) receivedObject)
							.equals("get live feed voters list")) {
						List<Vote> liveFeedVotersList = userDAO.latestVotes();
						objectOut.writeObject(liveFeedVotersList);

						objectOut.flush();
					}

				} else if (receivedObject.getClass() == RegistrationDetails.class) {
					RegistrationDetails registrationDetails = (RegistrationDetails) receivedObject;

					if (userDAO.getUserByEmail(registrationDetails.getEmail()) != null) {
						objectOut.writeObject("already registered");
					} else {
						String activationCode = userDAO
								.register(registrationDetails);
						String emailText = "Please use this code to activate your account: "
								+ activationCode;
						Email email = new Email("ss11.security@gmail.com",
								"Voting System", "Activation code", emailText,
								registrationDetails.getEmail());
						if (registrationDetails.getEmail().endsWith(
								"guc.edu.eg")) {
							email.send();
						}
						objectOut.writeObject("registered");
					}
					objectOut.flush();
				}

				else if (receivedObject.getClass() == LoginDetails.class) {
					LoginDetails loginDetails = (LoginDetails) receivedObject;

					User user = userDAO.getUserByLoginDetails(loginDetails);

					if (user == null) {
						objectOut.writeObject("invalid user");
					} else {
						this.userID = user.getId();
						if (user.isAdmin()) {
							objectOut.writeObject("admin user");
						} else if (!user.isActivated()) {
							objectOut.writeObject("account not activated");
						} else {
							objectOut.writeObject("valid user");
						}

					}
					objectOut.flush();
				}

				else if (receivedObject.getClass() == ActivationDetails.class) {
					ActivationDetails activationDetails = (ActivationDetails) receivedObject;

					User user = userDAO.getUserByLoginDetails(activationDetails
							.getLoginDetails());
					if (user == null
							|| !user.getActivationCode().equals(
									activationDetails.getActivationCode())) {
						objectOut.writeObject("invalid activation details");
					} else {
						this.userID = user.getId();
						user.setActivated(true);
						userDAO.commitUser(user);
						objectOut.writeObject("user activated");
					}

					objectOut.flush();
				}

				else if (receivedObject.getClass() == VoteDetails.class) {
					VoteDetails voteDetails = (VoteDetails) receivedObject;

					if (voteDetails.getUserSession().getExpiryDate().before(
							new Date())) {
						objectOut
								.writeObject("Sorry your session has expired. Please login and vote again.");
					} else {
						User user = userDAO.getUserBySessionId(voteDetails
								.getUserSession().getSessionID());
						user.setVote(voteDetails.getEncryptedVote());
						user.setVoteTime(new Date());
						userDAO.commitUser(user);
						objectOut
								.writeObject("Your vote has been submitted. Please save the encrypted version of it: "
										+ user.getVote());
					}
					objectOut.flush();
				}

				else if (receivedObject.getClass() == AdminControlMessage.class) {
					AdminControlMessage adminControlMessage = (AdminControlMessage) receivedObject;

					if (adminControlMessage.getUserSession().getExpiryDate()
							.before(new Date())) {
						objectOut
								.writeObject("Sorry your session has expired. Please login and try again.");
					} else {
						User user = userDAO
								.getUserBySessionId(adminControlMessage
										.getUserSession().getSessionID());

						if (!user.isAdmin()) {
							objectOut
									.writeObject("Sorry you are not an admin and do not have the permission to preform this task.");
						} else {
							if (adminControlMessage.getMessage().equals(
									"Start the voting process")) {
								List<Candidate> candidatesList = candidateDAO
										.getAllCandidates();
								this.server
										.setVotingStartDate(new GregorianCalendar());
								userDAO.resetAllVotes();
								Paillier.generateKeys(new BigInteger(Integer
										.toString(this.server
												.getMaxNumberOfVoters())),
										candidatesList);
								List<Candidate> updatedCandidates = Paillier
										.getCandidates();
								for (Candidate candidate : updatedCandidates) {
									candidateDAO.commitCandidate(candidate);
								}

								this.server.setPublicKey(Paillier
										.getPublicKey());
								this.server.setPrivateKey(Paillier
										.getPrivateKey());
								objectOut.writeObject("Voting has started");
							}

							if (adminControlMessage.getMessage().equals(
									"End the voting process")
									&& this.server.getVotingStartDate() != null) {
								this.server
										.setVotingEndDate(new GregorianCalendar());
								objectOut.writeObject("Voting has ended");
							} else if (adminControlMessage.getMessage()
									.startsWith("add")) {
								String[] splittedList = adminControlMessage
										.getMessage().split(":");
								Candidate newCandidate = new Candidate();
								newCandidate.setName(splittedList[1]);
								candidateDAO.addCandidate(newCandidate);
								objectOut.writeObject("Candidate added");
							}
							if (adminControlMessage.getMessage().equals(
									"get voting status")) {
								if (this.server.getVotingStartDate() == null) {
									objectOut
											.writeObject("Voting hasn't started");
								} else if (this.server.getVotingEndDate() != null) {
									objectOut.writeObject("Voting has closed");
								} else {
									objectOut.writeObject("Voting has started");
								}
							} else if (adminControlMessage.getMessage().equals(
									"get candidates list")) {
								List<Candidate> candidatesList = candidateDAO
										.getAllCandidates();
								List<String> candidatesName = new ArrayList<String>();
								for (Candidate candidate : candidatesList) {
									candidatesName.add(candidate.getName());
								}
								objectOut.writeObject(candidatesName);
							}
						}
					}
					objectOut.flush();
				}

			}

		} catch (Exception e) {
		}
	}

	private int getRandomSessionID() {
		int random = (int) ((Math.random() * 1000000000));
		return random;
	}
}
